package com.security.common.code;

import com.security.common.code.model.ValidateCodeType;
import com.security.common.code.sms.ValidateCode;
import com.security.customer.exception.ValidateCodeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * @Author: TongRui乀
 * @Date: 2020/3/21 16:34
 * @description：
 */
@Slf4j
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {


    /**
     *  封装所有的ValidateCodeGenerator 接口的实现类
     */
    @Autowired
    private Map<String, ValidateCodeGenerator> validateCodeGenerators;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void create(ServletWebRequest request) {
        // validateGenerator 生成验证码
        C validateCode = generator(request);
        // 缓存验证码
        save(validateCode,request);
        // 发送验证码
        send(validateCode,request);
    }

    /**
     *  发送验证码
     */
    protected abstract void send(ValidateCode validateCode, ServletWebRequest request);

    /**
     *  缓存验证的逻辑
     * @param validateCode
     */
    protected void save(C validateCode, ServletWebRequest request){

        ValidateCodeType validateCodeType = getValidateCodeType(request);

        String sessionId = request.getSessionId();

        String key = validateCodeType.getParamNameOnValidate() +"_"+ sessionId;

        this.stringRedisTemplate.opsForValue().set(key, validateCode.getCode());

        long expire = validateCode.getExpireTime().toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        // 设置过期时间
        this.stringRedisTemplate.expire(key,expire, TimeUnit.SECONDS);
    }

    /**
     *  获取验证码的类型
     * @param request
     * @return
     */
    protected ValidateCodeType getValidateCodeType(ServletWebRequest request){

        String type = StringUtils.substringBefore(getClass().getSimpleName(), "ValidateCodeProcessor");
        return ValidateCodeType.valueOf(type.toUpperCase());
    }

    /**
     *  生成验证码
     * @param request
     * @return
     */
    protected  C generator(ServletWebRequest request){

        String validateCodeType = getValidateCodeType(request).toString().toLowerCase();

        String key = validateCodeType + ValidateCodeGenerator.class.getSimpleName();

        ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(key);

        if(validateCodeGenerator == null){
            log.info("验证码生成器：{} 不存在", key);
            throw new ValidateCodeException("验证码生成器" + key + "不存在");
        }

        return (C) validateCodeGenerator.generate(request);

    }

    @Override
    public void validate(ServletWebRequest request) {
        ValidateCodeType processorType = getValidateCodeType(request);
        // 获取缓存中的验证码
        String key = processorType.getParamNameOnValidate() + "_" + request.getSessionId();
        String codeInSession = this.stringRedisTemplate.opsForValue().get(key);

        String codeInRequest;
        try {
            codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
                    processorType.getParamNameOnValidate());
        } catch (ServletRequestBindingException e) {
            throw new ValidateCodeException("获取验证码的值失败");
        }

        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException(processorType + "验证码的值不能为空");
        }

        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException(processorType + "验证码已过期");
        }

        if (!StringUtils.equals(codeInSession, codeInRequest)) {
            throw new ValidateCodeException(processorType + "验证码不匹配");
        }

        this.stringRedisTemplate.delete(key);
    }
}
